package com.itheima.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.itheima.common.DefaultException;
import com.itheima.common.Paging;
import com.itheima.convert.UserConvert;
import com.itheima.dto.user.*;
import com.itheima.entity.Organization;
import com.itheima.entity.User;
import com.itheima.enums.OrganizationEnum;
import com.itheima.enums.RedisEnum;
import com.itheima.mapper.UserMapper;
import com.itheima.service.OrganizationService;
import com.itheima.service.UserService;
import com.itheima.utils.*;
import com.itheima.vo.BriefUserVo;
import com.itheima.vo.organization.OrganizationVo;
import com.itheima.vo.user.AllUserVo;
import com.itheima.vo.user.DetailedUserVo;
import com.itheima.vo.user.UserPersonVo;
import com.itheima.vo.user.UserVo;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 51585
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2022-10-01 22:00:51
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private DefaultKaptcha captchaProducer;
    @Resource
    private SmsUtil smsUtil;
    @Resource
    private OssUtil ossUtil;
    @Resource
    private OrganizationTreeUtil organizationTreeUtil;
    @Resource
    private OrganizationService organizationService;
    @Resource
    private UserService userService;
    @Resource
    private UserConvert userConvert;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private UserMapper userMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public UserVo loginByAccount(AccountLoginDto dto) throws Exception {
        String password = DigestUtils.md5Hex(dto.getPassword());
        User user = this.getById(dto.getId());
        if (user == null) {
            throw new DefaultException("账号不存在");
        }
        if (!user.getPassword().equals(password)) {
            throw new DefaultException("账号或密码错误");
        }
        UserVo userVo = userConvert.toUserVo(user);
        String token = JwtUtil.createStudentAccessToken(user.getId());
        userVo.setToken(token);
        String refreshToken = JwtUtil.createStudentRefreshToken(user.getId());
        userVo.setRefreshToken(refreshToken);
        stringRedisTemplate.opsForValue().set(RedisEnum.LOGIN_CHECK.getRedis()+user.getId(), token);
        return userVo;
    }

    @Override
    public String getCaptchaImg(String phone, HttpServletResponse response) throws IOException {
        ByteArrayOutputStream imgOutputStream = new ByteArrayOutputStream();
        String imageCode = captchaProducer.createText();
        stringRedisTemplate.opsForValue().set(RedisEnum.PHOTO_CODE.getRedis() + phone, imageCode, 5 * 60, TimeUnit.SECONDS);
        BufferedImage challenge = captchaProducer.createImage(imageCode);
        ImageIO.write(challenge, "jpg", imgOutputStream);
        Base64.Encoder encoder = Base64.getEncoder();
        String res = encoder.encodeToString(imgOutputStream.toByteArray());
        imgOutputStream.close();
        return res;
    }

    @Override
    public UserVo loginByPhone(PhoneLoginDto dto) throws Exception {
        String phone = dto.getPhone();
        String phoneCode = dto.getPhoneCode();
        String imageCode = dto.getImageCode();
        User user = this.lambdaQuery().eq(User::getPhone, phone).one();
        if (user == null) {
            throw new DefaultException("没有该手机号对应的用户");
        }
        if (!imageCode.equals(stringRedisTemplate.opsForValue().get(RedisEnum.PHOTO_CODE.getRedis() + phone))) {
            throw new DefaultException("图片验证码错误");
        }
        if (!phoneCode.equals(stringRedisTemplate.opsForValue().get(RedisEnum.PHONE_CODE.getRedis() + phone))) {
            throw new DefaultException("手机验证码错误");
        }
        UserVo userVo = userConvert.toUserVo(user);
        String token = JwtUtil.createStudentAccessToken(user.getId());
        userVo.setToken(token);
        String refreshToken = JwtUtil.createStudentRefreshToken(user.getId());
        userVo.setRefreshToken(refreshToken);
        stringRedisTemplate.opsForValue().set(RedisEnum.LOGIN_CHECK.getRedis()+user.getId(), token);
        return userVo;
    }

    @Override
    public void getPhoneCode(PhoneLoginBaseParam dto) throws Exception {
        final int codeLength = 4;
        final String OK = "OK";
        String phone = dto.getPhone();
        String imageCode = dto.getImageCode();
        if (!imageCode.equals(stringRedisTemplate.opsForValue().get(RedisEnum.PHOTO_CODE.getRedis() + phone))) {
            throw new DefaultException("图片验证码错误");
        }
        User user = this.lambdaQuery().eq(User::getPhone, phone).one();
        if (Objects.isNull(user)) {
            throw new DefaultException("没有该手机号对应的用户");
        }
        Random random = new Random();
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < codeLength; i++) {
            stringBuilder.append(random.nextInt(10));
        }
        String code = stringBuilder.toString();
        String res = smsUtil.sendPhoneCode(code, phone);
        if (!OK.equals(res)) {
            throw new DefaultException("手机验证码发送失败");
        }
        stringRedisTemplate.opsForValue().set(RedisEnum.PHONE_CODE.getRedis() + phone, code, 5 * 60, TimeUnit.SECONDS);
    }

    @Override
    public String updateUserMessage(UpdateUserMessageDto dto) throws Exception {
        String userId = CheckUtil.checkLogin();
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.eq(User::getId, userId);
        if (StringUtils.isEmpty(dto.getPassword()) && StringUtils.isEmpty(dto.getPhone())) {
            throw new DefaultException("没有输入修改的内容");
        }
        if (!StringUtils.isEmpty(dto.getPassword())) {
            lambdaUpdateWrapper.set(User::getPassword, DigestUtils.md5Hex(dto.getPassword()));
        }
        if (!StringUtils.isEmpty(dto.getPhone())) {
            lambdaUpdateWrapper.set(User::getPhone, dto.getPhone());
        }
        this.update(lambdaUpdateWrapper);
        return "更新成功";
    }

    @Override
    public void updateImage(MultipartFile updateFile) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104253";
        CheckUtil.checkImageType(updateFile);
        String imageUrl = ossUtil.updateUserImageUrl(userId, updateFile);
        if (imageUrl.equals(false)) {
            throw new DefaultException("更新失败");
        }
        this.lambdaUpdate()
                .eq(User::getId, userId)
                .set(User::getImageUrl, imageUrl)
                .update();
    }

    @Override
    public DetailedUserVo showUserMessage() throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104254";
        User user = this.lambdaQuery()
                .eq(User::getId, userId)
                .one();
        Optional.ofNullable(user).orElseThrow(()->new DefaultException("用户有误，请核对"));
        Object o = redisTemplate.opsForValue().get(RedisEnum.ORGANIZATION_PARENT.getRedis() + user.getOrganizationId());
        List<String> parentOrganization;
        if (StringUtils.isEmpty(o)) {
            parentOrganization = organizationTreeUtil.getParentOrganization(user.getOrganizationId());
            redisTemplate.opsForValue().set(RedisEnum.ORGANIZATION_PARENT.getRedis() + user.getOrganizationId(), JSONUtil.toJsonStr(parentOrganization));
        } else {
            parentOrganization = JSONObject.parseObject(o.toString(), List.class);
        }
        DetailedUserVo detailedUserVo = new DetailedUserVo();
        UserPersonVo userPersonVo = userConvert.toUserPersonVo(user);
        detailedUserVo.setUserPersonVo(userPersonVo);
        detailedUserVo.setHierarchy(parentOrganization);
        return detailedUserVo;
    }

    @Override
    public boolean isAdministratorsOrSchool(String userId) {
        User user = this.lambdaQuery()
                .select(User::getOrganizationId)
                .eq(User::getId, userId)
                .one();
        if (user.getOrganizationId() == OrganizationEnum.school.getOrganizationId()
                || user.getOrganizationId() == OrganizationEnum.administrators.getOrganizationId()) {
            return true;
        }
        return false;
    }

    @Override
    public List<BriefUserVo> getBriefUser(List<String> userIds) {
        List<User> list = this.lambdaQuery()
                .select(User::getId, User::getName, User::getImageUrl)
                .in(User::getId, userIds)
                .list();
        return userConvert.userToBriefUserVo(list);
    }

    @Override
    public OrganizationVo showAllUsers() {
        Object o = redisTemplate.opsForValue().get(RedisEnum.ORGANIZATION_STRUCTURE_COMPLETE.getRedis());
        OrganizationVo organizationVo;
        if (StringUtils.isEmpty(o)) {
            organizationVo = organizationTreeUtil.create();
            redisTemplate.opsForValue().set(RedisEnum.ORGANIZATION_STRUCTURE_COMPLETE.getRedis(), JSONUtil.toJsonStr(organizationVo));
        } else {
            organizationVo = JSONObject.parseObject(o.toString(), OrganizationVo.class);
        }
        return organizationVo;
    }

    @Override
    public void excelImport(MultipartFile file) throws Exception {
        readExcel(file);
    }

    @Override
    public void deleteUser(List<String> userIdList) throws Exception {
        checkUserExist(userIdList);
        List<User> userList = this.lambdaQuery()
                .select(User::getOrganizationId)
                .in(User::getId, userIdList)
                .isNull(User::getDeletedAt)
                .list();
        List<Integer> collect = userList.stream()
                .map(User::getOrganizationId)
                .distinct()
                .collect(Collectors.toList());
        List<Organization> organizationList = organizationService.lambdaQuery()
                .select(Organization::getId, Organization::getLevel)
                .in(Organization::getId, collect)
                .isNull(Organization::getDeletedAt)
                .list();
        checkPower(organizationList);
        doDelete(userIdList);
    }

    @Override
    public Paging<AllUserVo> showAllUser(AllUserDto dto) {
        Page<?> page = new Page<>(dto.getPageIndex(), dto.getPageSize());
        IPage<AllUserVo> iPage = userMapper.showAllUser(page, dto);
        if (!CollectionUtils.isEmpty(iPage.getRecords())) {
            supplyOrganizationMsg(iPage.getRecords());
        }
        return PageUtils.toPage(iPage);
    }

    @Override
    public void resetPassword(String userId) throws Exception {
        String s = CheckUtil.checkLogin();
        User user = this.getById(s);
        if (!user.getOrganizationId().equals(OrganizationEnum.administrators.getOrganizationId()) &&
                !user.getOrganizationId().equals(OrganizationEnum.school.getOrganizationId())) {
            throw new DefaultException("没有权限");
        }
        final String originPassword="123456";
        this.lambdaUpdate()
                .set(User::getPassword,DigestUtils.md5Hex(originPassword))
                .eq(User::getId,userId)
                .update();
    }

    public void supplyOrganizationMsg(List<AllUserVo> list) {
        List<Integer> idList = list.stream().map(AllUserVo::getOrganizationId).distinct().collect(Collectors.toList());
        List<Organization> list1 = organizationService.lambdaQuery()
                .select(Organization::getId, Organization::getName)
                .in(Organization::getId, idList)
                .isNull(Organization::getDeletedAt)
                .list();
        Map<Integer, String> map = list1.stream().collect(Collectors.toMap(Organization::getId, Organization::getName));
        list.forEach(k -> {
            String name = map.get(k.getOrganizationId());
            Optional.ofNullable(name).ifPresent(k::setOrganization);
        });
    }

    public void doDelete(List<String> userIdList) throws DefaultException {
        boolean update = this.lambdaUpdate()
                .set(User::getDeletedAt, LocalDateTime.now())
                .in(User::getId, userIdList)
                .update();
        if (!update) {
            throw new DefaultException("删除失败");
        }

    }


    /**
     * 检查用户是否存在
     *
     * @param userIdList
     * @return
     * @throws DefaultException
     */
    public void checkUserExist(List<String> userIdList) throws DefaultException {
        List<User> userList = this.lambdaQuery()
                .select(User::getId, User::getOrganizationId)
                .in(User::getId, userIdList)
                .isNull(User::getDeletedAt)
                .list();
        if (userList.size() != userIdList.size()) {
            throw new DefaultException("选择了非法用户");
        }
    }

    public void readExcel(MultipartFile file) throws Exception {
        CheckUtil.checkFileType(file);
        try {
            List<ExcelUserDto> list = EasyExcel
                    .read(file.getInputStream())
                    .head(ExcelUserDto.class)
                    .doReadAllSync();
            List<Organization> organizationList = checkExcelContent(list);
            checkPower(organizationList);
            doSave(list);
        } catch (Exception e) {
            throw new DefaultException("excel导入失败" + e.getMessage());
        }
    }

    public void doSave(List<ExcelUserDto> list) throws DefaultException {
        Map<String, Integer> map = buildMap(list);
        List<String> collect = list.stream().map(ExcelUserDto::getId).collect(Collectors.toList());
        Integer count = this.lambdaQuery()
                .select(User::getId)
                .in(User::getId, collect)
                .isNull(User::getDeletedAt)
                .count();
        if (count != 0) {
            throw new DefaultException("用户已存在");
        }

        List<User> userList = list.stream().map(excelUserDto -> {
            User user = userConvert.excelUserDtoToUser(excelUserDto, LocalDateTime.now());
            user.setOrganizationId(map.get(excelUserDto.getOrganization()));
            return user;
        }).collect(Collectors.toList());
        checkExist(userList);
        if (!userService.saveBatch(userList)) {
            throw new DefaultException("导入失败");
        }
    }

    /**
     * 检查导入数据的完整性
     *
     * @param list 用户集合
     * @throws DefaultException
     */
    public void checkExist(List<User> list) throws DefaultException {
        for (User user :
                list) {
            if (user.getId() == null) {
                throw new DefaultException("有用户学号为空");
            }
            if (user.getName() == null) {
                throw new DefaultException("有用户姓名为空");
            }
            if (user.getOrganizationId() == null) {
                throw new DefaultException("有用户所属组织为空");
            }
            if (user.getPhone() == null) {
                throw new DefaultException("有用户手机号为空");
            }
        }
    }

    /**
     * 组织信息map
     *
     * @param list
     * @return
     */
    private Map<String, Integer> buildMap(List<ExcelUserDto> list) {
        List<String> organizationName = getOrganizationName(list);
        List<Organization> organizationList = organizationService.lambdaQuery()
                .select(Organization::getId, Organization::getName)
                .in(Organization::getName, organizationName)
                .isNull(Organization::getDeletedAt)
                .list();
        Map<String, Integer> map = new HashMap<>(organizationList.size());
        organizationList.forEach(organization -> map.put(organization.getName(), organization.getId()));
        return map;
    }

    public List<Organization> checkExcelContent(List<ExcelUserDto> list) throws DefaultException {
        List<String> organizationName = getOrganizationName(list);
        List<Organization> organizationList = organizationService.lambdaQuery()
                .select(Organization::getId, Organization::getLevel)
                .in(Organization::getName, organizationName)
                .isNull(Organization::getDeletedAt)
                .list();
        if (organizationName.size() != organizationList.size()) {
            throw new DefaultException("存在不合理的组织");
        }

        return organizationList;
    }

    public List<String> getOrganizationName(List<ExcelUserDto> list) {
        List<String> organizationName = list.stream()
                .map(ExcelUserDto::getOrganization)
                .distinct()
                .collect(Collectors.toList());
        return organizationName;
    }

    private void checkPower(List<Organization> organizationList) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        User user = userService.getById(userId);
        if (!user.getOrganizationId().equals(OrganizationEnum.administrators.getOrganizationId())
                && !user.getOrganizationId().equals(OrganizationEnum.school.getOrganizationId())) {
            throw new DefaultException("权限不足");
        }

    }


}



